import socket
import json
from time import time
from time import sleep
from threading import Thread
from threading import Lock
import re

def msg(content,channel):
	'''
	构造发送给客户端的数据包
	content是内容
	channel是频道
	！！！！一定要修改成自己的ip和port！！！！address是此服务器的ip+port(端口)
	'''
	return json.dumps({
		"cmd":"msg",
		"content":{"text":content},
		"channel":channel,
		"address":("127.0.0.1",536)})

class ChatRoom:
	def __init__(self,room_name,online_users,online_users_mutex,socket):
		# 聊天室的名称
		self.room_name = room_name

		# 在线的聊天室用户
		# {(ip,port):{"alive":time(),"channel":[]}}
		# alive是可以忽略的信息，但不要自行修改
		self.online_users = online_users


		# 在操作self.online_users之前和之后一定要"加锁","释放"
		# 如:
		'''
		self.online_users_mutex.acquire()
		for addr in self.online_users.keys():
			...
		self.online_users_mutex.release()
		'''
		self.online_users_mutex = online_users_mutex

		# self.s.sendto(data.encode(),(ip,port))
		self.s = socket

	def go(self,addr,data):
		'''
		表示用户加入房间
		数据格式 {"cmd":"go","channel":channel}
		"channel" 是频道的名称
		'''

		# 默认发送"HELLO! WELCOME TO CHANNEL.SAY WHAT YOU WANT TO SAY :)\n"
		self.online_users_mutex.acquire()
		reponse = msg(content=f"HELLO! WELCOME TO {data['channel']}.SAY WHAT YOU WANT TO SAY :)\n\nONLINE_USERS:{[detail['nick'] for detail in self.online_users.values()]}\n\n",channel=data["channel"])
		self.s.sendto(reponse.encode(),addr)

		# 默认向其他用户发送"XXX JOIN THE CHANNEL!\n"
		for address,detail in self.online_users.items():
			if address != addr and data["channel"] in detail["channel"]:
				reponse = msg(content=f"{self.online_users[addr]['nick']} JOIN THE CHANNEL!\n",channel=data["channel"])
				self.s.sendto(reponse.encode(),address)
		self.online_users_mutex.release()

	def chat(self,addr,data):
		'''
		表示用户的发言
		数据格式 {"cmd":"chat","content":content,"channel":channel}
		content是说的内容
		channel是频道的名称
		'''

		# 默认如果这个用户已经加入了这个房间的话，就向所有用户发送"sb. :..."
		self.online_users_mutex.acquire()
		reponse = msg(content=f"{self.online_users[addr]['nick']} : {data['content']}\n",channel=data["channel"])
		for address,detail in self.online_users.items():
			if data["channel"] in detail["channel"]:
				self.s.sendto(reponse.encode(),address)
		self.online_users_mutex.release()

	def left(self,addr,data):
		'''
		用户离开房间
		数据格式 {"cmd":"left","channel":channel}
		channel是频道的名称
		'''

		# 默认向其他用户发送 "XXX LEFT THE CHANNEL!\n"
		self.online_users_mutex.acquire()
		for address,detail in self.online_users.items():
			if data["channel"] in detail["channel"]:
				reponse = msg(content=f"{self.online_users[addr]['nick']} LEFT THE CHANNEL!\n",channel=data["channel"])
				self.s.sendto(reponse.encode(),address)
		self.online_users_mutex.release()


class RoomHome(ChatRoom):
	def __init__(self,room_name,online_users,online_users_mutex,socket):
		ChatRoom.__init__(self,room_name,online_users,online_users_mutex,socket)
		self.room_name = room_name
		self.online_users = online_users
		self.online_users_mutex = online_users_mutex
		self.s = socket

	def chat(self,addr,data):
		content = data["content"].strip()
		cmd_pattern = r"/(.+)"

		cmd_match = re.findall(cmd_pattern,content)

		if cmd_match != []:
			cmd_list = cmd_match[0].split()
			cmd_list_len = len(cmd_list)

			if cmd_list[0] == "nick":
				if cmd_list_len == 2:
					nick_pattern = r"[a-zA-Z0-9_]{1,15}"
					nick_match = re.findall(nick_pattern,cmd_list[1])
					if nick_match != []:
						self.online_users_mutex.acquire()
						self.online_users[addr]["nick"] = nick_match[0]
						self.online_users_mutex.release()
		else:
			self.online_users_mutex.acquire()
			reponse = msg(content=f"{self.online_users[addr]['nick']} : {data['content']}\n",channel=data["channel"])
			for address,detail in self.online_users.items():
				if data["channel"] in detail["channel"]:
					self.s.sendto(reponse.encode(),address)
			self.online_users_mutex.release()

class Main:
	'''
	主要程序
	实现的是聊天室的功能
	'''
	def __init__(self):
		self.s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
		self.s.bind(("0.0.0.0",536))

		# 为了有序地操作下面的self.online_users，请在操作前.acquire()，并在操作完后.release()
		# 如:
		# self.online_users_mutex.acquire()
		# del self.online_users[("127.0.0.1",536)]
		# self.online_users_mutex.release()
		self.online_users_mutex = Lock()

		self.online_users = {}
		# 结构 -> {(ip,port):{"alive":time,"channel":[],"nick":"xxx"}}
		# 每一个元素的键表示address(地址，由ip和port组成)
		# alive是用户在线的标志（用户在线时，会每隔10秒给服务器发送一次
		# channel是用户所加入的房间，但是无法得知用户加入了其他的服务器的哪些房间
		# nick表示用户的名字，默认为ip,port

		# 这个线程用来判断用户是否还在线
		check_online = Thread(target=self.check_online)
		check_online.start()

		self.all_room = []

		# 在此处添加你的房间
		self.home = RoomHome("home",self.online_users,self.online_users_mutex,self.s)
		self.all_room.append(self.home)


		self.received()
	
	def received(self):
		'''
		接受数据
		'''
		while True:
			try:
				data,addr = self.s.recvfrom(2048)
				print("received from {}:{}".format(addr,data.decode()))

				data = json.loads(data.decode())
				self.handle(data,addr)
			except:
				print(f"接收到无法处理数据,来自{addr},内容为{data}")
	
	def check_online(self):
		'''
		判断用户是否在线
		根据alive的值
		'''
		while True:
			if self.online_users != {}:
				self.online_users_mutex.acquire()
				dead = ""
				for address,detail in self.online_users.items():
					if time() - detail["alive"] > 22:
						dead = address
				self.online_users_mutex.release()

				if dead != "":
					# 模拟处理用户离开的消息
					reponse_list = []
					self.online_users_mutex.acquire()
					for room in self.online_users[dead]["channel"]:
						reponse = {"cmd":"left","channel":room}
						reponse_list.append(reponse)
					self.online_users_mutex.release()

					for reponse in reponse_list:
						self.handle(reponse,dead)

					self.online_users_mutex.acquire()
					del self.online_users[dead]
					self.online_users_mutex.release()

					print(f"{dead} seem to leave")

	def handle(self,data,addr):
		'''
		处理所收到的数据
		data是json格式的
		addr表示数据的来源,address(ip,port)
		'''
		if data["cmd"] == "alive":
			'''
			用户在线标志 
			数据格式 {"cmd":"alive"}
			'''
			# 用户的alive表示最近的一次在线标识（只要连接了服务器就会发送
			self.online_users_mutex.acquire()
			if addr in self.online_users:
				self.online_users[addr]["alive"] = time()
			self.online_users_mutex.release()

		if data["cmd"] == "go":
			self.online_users_mutex.acquire()
			if addr in self.online_users:
				self.online_users[addr]["channel"].append(data["channel"])
			else:
				self.online_users[addr] = {"alive":time(),"channel":[data["channel"]],"nick":f"Guest-{addr[0]}"}
			self.online_users_mutex.release()

			if data["channel"] not in [room.room_name for room in self.all_room]:
				self.all_room.append(ChatRoom(data["channel"],self.online_users,self.online_users_mutex,self.s))
			for room in self.all_room:
				if room.room_name == data["channel"]:
					room.go(addr,data)

		if data["cmd"] == "left":
			self.online_users_mutex.acquire()
			if addr in self.online_users:
				if data["channel"] in self.online_users[addr]["channel"]:
					self.online_users[addr]["channel"].remove(data["channel"])
			self.online_users_mutex.release()

			for room in self.all_room:
				if room.room_name == data["channel"]:
					room.left(addr,data)

		if data["cmd"] == "chat":
			if addr in self.online_users:
				if data["channel"] in self.online_users[addr]["channel"]:
					for room in self.all_room:
						if room.room_name == data["channel"]:
							room.chat(addr,data)


if __name__ == '__main__':
	main = Main()
